#ifndef UNITTEST_H
#define UNITTEST_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <ctype.h>

#include <utils.h>

/* to use this file elsewhere, you may want to change OUTPUT */
#include <debug.h>

static inline void
_assert_equal( int (*cmp)(const void *, const void *),
	      void (*diff)(const void *, const void *),
	      const void *v1, const void *v2,
	      const char *file, const int line, const char *fname )
{
  if ( cmp( v1, v2 ) == 0 )
    {
      fprintf( stderr, "%s:%d:%s() Equality assertion failed:\n",
	       file, line, fname );
      diff( v1, v2 );
      exit( -1 );
    }
}

static inline void
_assert_different( int (*cmp)(const void *, const void *),
		   void (*diff)(const void *, const void *),
		   const void *v1, const void *v2,
		   const char *file, const int line, const char *fname )
{
  if ( cmp( v1, v2 ) != 0 )
    {
      fprintf( stderr, "%s:%d:%s() Inequality assertion failed:\n",
	       file, line, fname );
      diff( v1, v2 );
      exit( -1 );
    }
}


#define assert_equal( cmp, diff, type, v1, v2 ) \
  ({\
    sametype( (v1), (v2) );                                                   \
    typecheck( type, (v1) );                                                  \
    __typeof__(v1) _v1 = (v1);                                                \
    __typeof__(v2) _v2 = (v2);                                                \
    _assert_equal( (cmp), (diff), (void*)(&_v1), (void*)(&_v2),               \
                   __FILE__, __LINE__, __FUNCTION__ );                        \
   })

#define assert_different( cmp, diff, type, v1, v2 ) \
  ({\
    sametype( (v1), (v2) );                                                   \
    typecheck( type, (v1) );                                                  \
    __typeof__(v1) _v1 = (v1);                                                \
    __typeof__(v2) _v2 = (v2);                                                \
    _assert_different( (cmp), (diff), (void*)(&_v1), (void*)(&_v2),           \
                   __FILE__, __LINE__, __FUNCTION__ );                        \
   })


static inline void
no_diff( const void *a, const void *b )
{
  fprintf( stderr, "DIFF: ????????\n" );
}


static inline void
string_diff( const void *v1, const void *v2 )
{
  unsigned long i;
  const char *s1 = *(char**)v1;
  const char *s2 = *(char**)v2;

  if ( ( s1 == NULL ) && ( s2 != NULL ) )
    {
      fprintf( stderr, "DIFF: first string is NULL, but the second is not\n" );
      return;
    }

  if ( ( s1 != NULL ) && ( s2 == NULL ) )
    {
      fprintf( stderr, "DIFF: first string is not NULL, but the second is\n" );
      return;
    }
  for ( i = 0;
	( ( s1[ i ] != '\0' ) &&
	  ( s2[ i ] != '\0' ) &&
	  ( s1[ i ] == s2[ i ] ) );
	i ++ );

  if ( s1[ i ] != s2[ i ] )
    {
      unsigned long begin, end, j;

      fprintf( stderr, "DIFF: strings differ at position %lu\n", i );

      if ( i < 20 )
	begin = 0;
      else
	begin = i - 20;

      end = i + 20;

      fprintf( stderr, "DIFF: 1) \"%s", ( begin != 0 ) ? "... " : "    " );
      for ( j = begin; ( j < end ) && ( s1[ j ] != '\0' ); j++ )
	fprintf( stderr, "%c", s1[ j ] );
      fprintf( stderr, "%s\"\n", ( s1[ j ] != '\0' ) ? " ..." : "" );

      fprintf( stderr, "DIFF: 2) \"%s", ( begin != 0 ) ? "... " : "    " );
      for ( j = begin; ( j < end ) && ( s2[ j ] != '\0' ); j++ )
	fprintf( stderr, "%c", s2[ j ] );
      fprintf( stderr, "%s\"\n", ( s2[ j ] != '\0' ) ? " ..." : "" );

      fprintf( stderr, "DIFF:         " );
      for ( j = begin; j < i; j ++ )
	fprintf( stderr, "-" );
      fprintf( stderr, "^\n" );

    }
  else
    fprintf( stderr, "DIFF: strings are equal!\n" );
}

static inline int
cmp_str( const void *a, const void *b )
{
  const char *i = *(char **)a;
  const char *j = *(char **)b;
  return strcmp( i, j ) == 0;
}

#define assert_equal_str( a, b )                                              \
  assert_equal( cmp_str, string_diff, char*, (a), (b) )

#define assert_different_str( a, b )                                          \
  assert_different( cmp_str, string_diff, char*, (a), (b) )



static inline void
int_diff( const void *a, const void *b )
{
  int i = *(int*)a;
  int j = *(int*)b;
  fprintf( stderr, "DIFF: 1) %d\nDIFF: 2) %d\n", i, j );
}

static inline int
cmp_int( const void *a, const void *b )
{
  const int i = *(int*)a;
  const int j = *(int*)b;
  return i == j;
}

#define assert_equal_int( a, b ) \
  assert_equal( cmp_int, int_diff, int, (a), (b) )

#define assert_different_int( a, b ) \
  assert_different( cmp_int, int_diff, int, (a), (b) )





static inline void
uint_diff( const void *a, const void *b )
{
  unsigned int i = *(unsigned int*)a;
  unsigned int j = *(unsigned int*)b;
  fprintf( stderr, "DIFF: 1) %u\nDIFF: 2) %u\n", i, j );
}

static inline int
cmp_uint( const void *a, const void *b )
{
  const unsigned int i = *(unsigned int*)a;
  const unsigned int j = *(unsigned int*)b;
  return i == j;
}

#define assert_equal_uint( a, b ) \
  assert_equal( cmp_uint, uint_diff, unsigned int, (a), (b) )

#define assert_different_uint( a, b ) \
  assert_different( cmp_uint, uint_diff, unsigned int, (a), (b) )

static inline void
hex_diff( const void *a, const void *b )
{
  unsigned int i = *(unsigned int*)a;
  unsigned int j = *(unsigned int*)b;
  fprintf( stderr, "DIFF: 1) %x\nDIFF: 2) %x\n", i, j );
}

#define assert_equal_hex( a, b ) \
  assert_equal( cmp_uint, hex_diff, unsigned int, (a), (b) )

#define assert_different_hex( a, b ) \
  assert_different( cmp_uint, hex_diff, unsigned int, (a), (b) )






static inline void
uint8_diff( const void *a, const void *b )
{
  uint8_t i = *(uint8_t*)a;
  uint8_t j = *(uint8_t*)b;
  fprintf( stderr, "DIFF: 1) %u\nDIFF: 2) %u\n", i, j );
}

static inline int
cmp_uint8( const void *a, const void *b )
{
  const uint8_t i = *(uint8_t*)a;
  const uint8_t j = *(uint8_t*)b;
  return i == j;
}

#define assert_equal_uint8( a, b ) \
  assert_equal( cmp_uint8, uint8_diff, uint8_t, (a), (b) )

#define assert_different_uint8( a, b ) \
  assert_different( cmp_uint8, uint8_diff, uint8_t, (a), (b) )

static inline void
hex8_diff( const void *a, const void *b )
{
  uint8_t i = *(uint8_t*)a;
  uint8_t j = *(uint8_t*)b;
  fprintf( stderr, "DIFF: 1) %x\nDIFF: 2) %x\n", i, j );
}

#define assert_equal_hex8( a, b ) \
  assert_equal( cmp_uint8, hex8_diff, uint8_t, (a), (b) )

#define assert_different_hex8( a, b ) \
  assert_different( cmp_uint8, hex8_diff, uint8_t, (a), (b) )





static inline void
uint16_diff( const void *a, const void *b )
{
  uint16_t i = *(uint16_t*)a;
  uint16_t j = *(uint16_t*)b;
  fprintf( stderr, "DIFF: 1) %u\nDIFF: 2) %u\n", i, j );
}

static inline int
cmp_uint16( const void *a, const void *b )
{
  uint16_t i = *(uint16_t*)a;
  uint16_t j = *(uint16_t*)b;
  return i == j;
}

#define assert_equal_uint16( a, b ) \
  assert_equal( cmp_uint16, uint16_diff, uint16_t, (a), (b) )

#define assert_different_uint16( a, b ) \
  assert_different( cmp_uint16, uint16_diff, uint16_t, (a), (b) )

static inline void
hex16_diff( const void *a, const void *b )
{
  uint16_t i = *(uint16_t*)a;
  uint16_t j = *(uint16_t*)b;
  fprintf( stderr, "DIFF: 1) %x\nDIFF: 2) %x\n", i, j );
}

#define assert_equal_hex16( a, b ) \
  assert_equal( cmp_uint16, hex16_diff, uint16_t, (a), (b) )

#define assert_different_hex16( a, b ) \
  assert_different( cmp_uint16, hex16_diff, uint16_t, (a), (b) )





static inline void
uint32_diff( const void *a, const void *b )
{
  uint32_t i = *(uint32_t*)a;
  uint32_t j = *(uint32_t*)b;
  fprintf( stderr, "DIFF: 1) %u\nDIFF: 2) %u\n", i, j );
}

static inline int
cmp_uint32( const void *a, const void *b )
{
  uint32_t i = *(uint32_t*)a;
  uint32_t j = *(uint32_t*)b;
  return i == j;
}

#define assert_equal_uint32( a, b ) \
  assert_equal( cmp_uint32, uint32_diff, uint32_t, (a), (b) )

#define assert_different_uint32( a, b ) \
  assert_different( cmp_uint32, uint32_diff, uint32_t, (a), (b) )

static inline void
hex32_diff( const void *a, const void *b )
{
  uint32_t i = *(uint32_t*)a;
  uint32_t j = *(uint32_t*)b;
  fprintf( stderr, "DIFF: 1) %x\nDIFF: 2) %x\n", i, j );
}

#define assert_equal_hex32( a, b ) \
  assert_equal( cmp_uint32, hex32_diff, uint32_t, (a), (b) )

#define assert_different_hex32( a, b ) \
  assert_different( cmp_uint32, hex32_diff, uint32_t, (a), (b) )



static inline void
long_diff( const void *a, const void *b )
{
  long i = *(long*)a;
  long j = *(long*)b;
  fprintf( stderr, "DIFF: 1) %ld\nDIFF: 2) %ld\n", i, j );
}

static inline int
cmp_long( const void *a, const void *b )
{
  long i = *(long*)a;
  long j = *(long*)b;
  return i == j;
}

#define assert_equal_long( a, b ) \
  assert_equal( cmp_long, long_diff, long, (a), (b) )

#define assert_different_long( a, b ) \
  assert_different( cmp_long, long_diff, long, (a), (b) )




static inline void
ulong_diff( const void *a, const void *b )
{
  unsigned long i = *(unsigned long*)a;
  unsigned long j = *(unsigned long*)b;
  fprintf( stderr, "DIFF: 1) %lu\nDIFF: 2) %lu\n", i, j );
}

static inline int
cmp_ulong( const void *a, const void *b )
{
  unsigned long i = *(unsigned long*)a;
  unsigned long j = *(unsigned long*)b;
  return i == j;
}

#define assert_equal_ulong( a, b ) \
  assert_equal( cmp_ulong, ulong_diff, unsigned long, (a), (b) )

#define assert_different_ulong( a, b ) \
  assert_different( cmp_ulong, ulong_diff, unsigned long, (a), (b) )





static inline void
longlong_diff( const void *a, const void *b )
{
  long long i = *(long long*)a;
  long long j = *(long long*)b;
  fprintf( stderr, "DIFF: 1) %lld\nDIFF: 2) %lld\n", i, j );
}

static inline int
cmp_longlong( const void *a, const void *b )
{
  long long i = *(long long*)a;
  long long j = *(long long*)b;
  return i == j;
}

#define assert_equal_longlong( a, b ) \
  assert_equal( cmp_longlong, longlong_diff, long long, (a), (b) )

#define assert_different_longlong( a, b ) \
  assert_different( cmp_longlong, longlong_diff, long long, (a), (b) )





static inline void
ulonglong_diff( const void *a, const void *b )
{
  unsigned long long i = *(unsigned long long*)a;
  unsigned long long j = *(unsigned long long*)b;
  fprintf( stderr, "DIFF: 1) %llu\nDIFF: 2) %llu\n", i, j );
}

static inline int
cmp_ulonglong( const void *a, const void *b )
{
  unsigned long long i = *(unsigned long long*)a;
  unsigned long long j = *(unsigned long long*)b;
  return i == j;
}

#define assert_equal_ulonglong( a, b ) \
  assert_equal( cmp_ulonglong, ulonglong_diff, unsigned long long, (a), (b) )


#define assert_different_ulonglong( a, b ) \
  assert_different( cmp_ulonglong, ulonglong_diff, unsigned long long, (a),(b))



static inline void
_assert_equal_double( const double a, const double b, const double threshold,
		      const char *file, const int line, const char *fname )
{
  if ( ! double_is_equal( a, b, threshold ) )
    {
      fprintf( stderr, "%s:%d:%s() Equality assertion failed:\n",
	       file, line, fname );
      fprintf( stderr, "DIFF: 1) %lf\nDIFF: 2) %lf\n", a, b );
      exit( -1 );
    }
}
#define assert_equal_double( a, b, threshold ) \
  _assert_equal_double( a, b, threshold, __FILE__, __LINE__, __FUNCTION__ )

static inline void
_assert_different_double( const double a, const double b,
			  const double threshold,
			  const char *file, const int line, const char *fname )
{
  if ( double_is_equal( a, b, threshold ) )
    {
      fprintf( stderr, "%s:%d:%s() Inequality assertion failed:\n",
	       file, line, fname );
      fprintf( stderr, "DIFF: 1) %lf\nDIFF: 2) %lf\n", a, b );
      exit( -1 );
    }
}
#define assert_different_double( a, b, threshold ) \
  _assert_different_double( a, b, threshold, __FILE__, __LINE__, __FUNCTION__ )




static inline void
pointer_diff( const void *a, const void *b )
{
  int *i = *(int**)a;
  int *j = *(int**)b;
  fprintf( stderr, "DIFF: 1) %p\nDIFF: 2) %p\n", i, j );
}

static inline int
cmp_pointer( const void *a, const void *b )
{
  int *p1 = *(int**)a;
  int *p2 = *(int**)b;
  return p1 == p2;
}

#define assert_equal_pointer( a, b ) \
  assert_equal( cmp_pointer, pointer_diff, void *, (void*)(a), (void*)(b) )

#define assert_different_pointer( a, b ) \
  assert_different( cmp_pointer, pointer_diff, void *, (void*)(a), (void*)(b) )


#define assert_not_null( p )                                                  \
  assert_different( cmp_pointer, pointer_diff, void *, (void*)(p), NULL )

#define assert_null( p )                                                      \
  assert_equal( cmp_pointer, pointer_diff, void *, (void*)(p), NULL )



/*
 * Some programs have results hard to verify automatically, it's easier to
 * ask the user if the result looks ok.
 *
 * We provide this facility here.
 */

/**
 * Ask the user a Yes/No question.
 *
 * @param yn_msg the message
 *
 * @return 1 if it 'Yes', 0 if 'No'
 */
int
ask_user_yn( const char *yn_msg )
{
  assert( yn_msg != NULL);

  printf( "%s [Y/n] ", yn_msg );
  int k;
  do
    k = getchar();
  while ( ( k != EOF ) && ( k != '\n' ) && ( k != '\r' ) &&
	  ( k != 'y' ) && ( k != 'Y' ) && ( k != 'n' ) && ( k != 'N' ) );

  if ( ( k == EOF ) || ( k == '\n' ) || ( k == '\r' ) )
    k = 'y';

  printf( "%c\n", k );

  return tolower( k ) == 'y';
}




#endif /* UNITTEST_H */
